﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DraggedObjectTransInfo
{
    public Vector3 position;
    public Quaternion rotation;
    public Vector3 localScale;
    public Vector3 forward;
    public Vector3 up;
    public Vector3 right;
}

public class BlenderView : MonoBehaviour
{
    // 操作状态
    class OperateState
    {
        public const int NONE = 0;
        public const int MOVE = 1;
        public const int SCALE = 2;
        public const int ROTATE = 3;
        public const int MOVE_X = 4;
        public const int MOVE_Y = 5;
        public const int MOVE_Z = 6;
        public const int SCALE_X = 7;
        public const int SCALE_Y = 8;
        public const int SCALE_Z = 9;
        public const int ROTATE_X = 10;
        public const int ROTATE_Y = 11;
        public const int ROTATE_Z = 12;
    }

    public class Axis
    {
        public const int AXIS_X = 0;
        public const int AXIS_Y = 1;
        public const int AXIS_Z = 2;
    }

    public Transform hitObject;

    public bool isWorldSpace = true;
    private int currentState;
    private DraggedObjectTransInfo targetObjectBeginTransInfo;
    private Vector3 targetObjectBeginOffset;
    private Camera targetCamera;

    private GLDraw glDraw;

    private float operationBeginDistance;
    private float operationBeginAngle;

    private List<GLLine2D> lines2D;
    private List<GLLine3D> lines3D;

    private Vector3 MousePosition
    {
        get
        {
            float hitObjectDistance = Vector3.Project(hitObject.position - targetCamera.transform.position, targetCamera.transform.forward).magnitude;
            return new Vector3(Input.mousePosition.x, Input.mousePosition.y, hitObjectDistance);
        }
    }

    private Vector3 HitObjectScreenPosition
    {
        get
        {
            return targetCamera.WorldToScreenPoint(hitObject.position);
        }
    }

    private float HitObjectScreenPosDistanceToMousePosV2
    {
        get
        {
            Vector2 mouseScreenPosition = new Vector2(MousePosition.x, MousePosition.y);
            Vector2 hitObjectScreenPosition = new Vector2(HitObjectScreenPosition.x, HitObjectScreenPosition.y);
            return (mouseScreenPosition - hitObjectScreenPosition).magnitude;
        }
    }

    void Start()
    {
        targetCamera = Camera.main;
        glDraw = new GLDraw();
        lines2D = new List<GLLine2D>();
        lines3D = new List<GLLine3D>();
    }

    void Update()
    {
        #region CameraView Align
        if (currentState == OperateState.NONE)
        {
            if (Input.GetKeyDown(KeyCode.G))
            {
                currentState = OperateState.MOVE;

                BackUpBeginTransInfo();
                targetObjectBeginOffset = targetObjectBeginTransInfo.position - targetCamera.ScreenToWorldPoint(MousePosition);
            }

            if (Input.GetKeyDown(KeyCode.R))
            {
                currentState = OperateState.ROTATE;

                BackUpBeginTransInfo();
                operationBeginAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);
                Debug.Log(operationBeginAngle);
            }
            if (Input.GetKeyDown(KeyCode.S))
            {
                currentState = OperateState.SCALE;

                BackUpBeginTransInfo();
                operationBeginDistance = HitObjectScreenPosDistanceToMousePosV2;
                if (operationBeginDistance == 0) operationBeginDistance = 1;
            }
        }
        #endregion

        #region Move Align Axis
        if (currentState == OperateState.MOVE)
        {
            Vector3 worldPosition = targetCamera.ScreenToWorldPoint(MousePosition);
            hitObject.position = worldPosition + targetObjectBeginOffset;

            MoveStateChangeCheck();
        }

        if (currentState == OperateState.MOVE_X)
        {
            MoveAlignAxis(Axis.AXIS_X);
            MoveStateChangeCheck();
        }

        if (currentState == OperateState.MOVE_Y)
        {
            MoveAlignAxis(Axis.AXIS_Y);
            MoveStateChangeCheck();
        }

        if (currentState == OperateState.MOVE_Z)
        {
            MoveAlignAxis(Axis.AXIS_Z);
            MoveStateChangeCheck();
        }
        #endregion

        #region Scale Align Axis
        if (currentState == OperateState.SCALE)
        {
            // 缩放 = 当前距离 / 初始距离
            float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
            float scaleRadio = currentOperationDistance / operationBeginDistance;
            hitObject.localScale = targetObjectBeginTransInfo.localScale * scaleRadio;

            ScaleStateChangeCheck();
        }

        if (currentState == OperateState.SCALE_X)
        {
            ScaleAlignAxis(Axis.AXIS_X);
            ScaleStateChangeCheck();
        }

        if (currentState == OperateState.SCALE_Y)
        {
            ScaleAlignAxis(Axis.AXIS_Y);
            ScaleStateChangeCheck();
        }

        if (currentState == OperateState.SCALE_Z)
        {
            ScaleAlignAxis(Axis.AXIS_Z);
            ScaleStateChangeCheck();
        }
        #endregion

        #region Rotate Align Axis
        if (currentState == OperateState.ROTATE)
        {
            // 注意Quaternion相乘的顺序：从右往左
            float currentOperationAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);
            Quaternion quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetCamera.transform.forward);
            hitObject.rotation = quater * targetObjectBeginTransInfo.rotation;

            RotateStateChangeCheck();
        }

        if (currentState == OperateState.ROTATE_X)
        {
            RotateAlignAxis(Axis.AXIS_X);
            RotateStateChangeCheck();
        }

        if (currentState == OperateState.ROTATE_Y)
        {
            RotateAlignAxis(Axis.AXIS_Y);
            RotateStateChangeCheck();
        }

        if (currentState == OperateState.ROTATE_Z)
        {
            RotateAlignAxis(Axis.AXIS_Z);
            RotateStateChangeCheck();
        }
        #endregion

        #region Others
        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
        {
            currentState = OperateState.NONE;
        }

        if (Input.GetMouseButtonDown(1))
        {
            ResetTargetObjectToBeginState();
        }
        #endregion
    }

    private void RotateStateChangeCheck()
    {
        if (Input.GetKeyDown(KeyCode.X))
        {
            currentState = OperateState.ROTATE_X;
        }
        else if (Input.GetKeyDown(KeyCode.Y))
        {
            currentState = OperateState.ROTATE_Y;
        }
        else if (Input.GetKeyDown(KeyCode.Z))
        {
            currentState = OperateState.ROTATE_Z;
        }
    }

    private void ScaleStateChangeCheck()
    {
        if (Input.GetKeyDown(KeyCode.X))
        {
            currentState = OperateState.SCALE_X;
        }
        else if (Input.GetKeyDown(KeyCode.Y))
        {
            currentState = OperateState.SCALE_Y;
        }
        else if (Input.GetKeyDown(KeyCode.Z))
        {
            currentState = OperateState.SCALE_Z;
        }
    }

    private void MoveStateChangeCheck()
    {
        if (Input.GetKeyDown(KeyCode.X))
        {
            currentState = OperateState.MOVE_X;
        }
        else if (Input.GetKeyDown(KeyCode.Y))
        {
            currentState = OperateState.MOVE_Y;
        }
        else if (Input.GetKeyDown(KeyCode.Z))
        {
            currentState = OperateState.MOVE_Z;
        }
    }

    private void MoveAlignAxis(int axis)
    {
        Ray ray = targetCamera.ScreenPointToRay(Input.mousePosition);
        Vector3 intersectPoint = Vector3.zero;

        if (isWorldSpace)
        {
            if (axis == Axis.AXIS_X)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.back, ray);
                hitObject.position = new Vector3(intersectPoint.x, targetObjectBeginTransInfo.position.y, targetObjectBeginTransInfo.position.z);
            }
            else if (axis == Axis.AXIS_Y)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.back, ray);
                hitObject.position = new Vector3(targetObjectBeginTransInfo.position.x, intersectPoint.y, targetObjectBeginTransInfo.position.z);
            }
            else if (axis == Axis.AXIS_Z)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.up, ray);
                hitObject.position = new Vector3(targetObjectBeginTransInfo.position.x, targetObjectBeginTransInfo.position.y, intersectPoint.z);
            }
        }
        else
        {
            // 计算射线与构建的平面的交点（平面上一个点以及平面的法线确定一个平面），将交点投影到局部坐标轴的对应轴上就可以得到当前物体需要移动到的位置
            Vector3 offsetVec = Vector3.zero;

            if (axis == Axis.AXIS_X)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.forward, ray);
                Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.right);
            }
            else if (axis == Axis.AXIS_Y)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.forward, ray);
                Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.up);
            }
            else if (axis == Axis.AXIS_Z)
            {
                intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.up, ray);
                Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.forward);
            }

            hitObject.position = targetObjectBeginTransInfo.position + offsetVec;
        }
    }

    private void ScaleAlignAxis(int axis)
    {
        float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
        float scaleRadio = currentOperationDistance / operationBeginDistance;

        if (axis == Axis.AXIS_X)
        {
            hitObject.localScale = new Vector3(scaleRadio, targetObjectBeginTransInfo.localScale.y, targetObjectBeginTransInfo.localScale.z);
        }
        else if (axis == Axis.AXIS_Y)
        {
            hitObject.localScale = new Vector3(targetObjectBeginTransInfo.localScale.x, scaleRadio, targetObjectBeginTransInfo.localScale.z);
        }
        else if (axis == Axis.AXIS_Z)
        {
            hitObject.localScale = new Vector3(targetObjectBeginTransInfo.localScale.x, targetObjectBeginTransInfo.localScale.y, scaleRadio);
        }
    }

    private void RotateAlignAxis(int axis)
    {
        float currentOperationAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);
        Quaternion quater = Quaternion.identity;

        if (isWorldSpace)
        {
            if (axis == Axis.AXIS_X)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.right);
            }
            else if (axis == Axis.AXIS_Y)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.up);
            }
            else if (axis == Axis.AXIS_Z)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.forward);
            }
        }
        else
        {
            if (axis == Axis.AXIS_X)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.right);
            }
            else if (axis == Axis.AXIS_Y)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.up);
            }
            else if (axis == Axis.AXIS_Z)
            {
                quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.forward);
            }
        }

        hitObject.rotation = quater * targetObjectBeginTransInfo.rotation;
    }

    private void BackUpBeginTransInfo()
    {
        targetObjectBeginTransInfo = new DraggedObjectTransInfo()
        {
            position = hitObject.position,
            localScale = hitObject.localScale,
            rotation = hitObject.rotation,
            forward = hitObject.forward,
            up = hitObject.up,
            right = hitObject.right,
        };
    }

    private void ResetTargetObjectToBeginState()
    {
        if (hitObject != null && targetObjectBeginTransInfo != null)
        {
            hitObject.position = targetObjectBeginTransInfo.position;
            hitObject.rotation = targetObjectBeginTransInfo.rotation;
            hitObject.localScale = targetObjectBeginTransInfo.localScale;
        }

        currentState = 0;
    }

    void OnPostRender()
    {
        if (currentState > 0)
        {
            if (lines2D.Count > 0)
            {
                lines2D.Clear();
            }

            if (lines3D.Count > 0)
            {
                lines3D.Clear();
            }

            // draw gizmos line
            Vector2 beginPosition = targetCamera.WorldToScreenPoint(targetObjectBeginTransInfo.position);

            if (currentState == OperateState.MOVE || currentState == OperateState.MOVE_X || currentState == OperateState.MOVE_Y || currentState == OperateState.MOVE_Z)
            {
                Vector2 currentPosition = HitObjectScreenPosition;
                lines2D.Add(new GLLine2D() { from = MapScreenPosTo01(beginPosition), to = MapScreenPosTo01(currentPosition) });
                glDraw.DrawLine2D(lines2D, Color.white);
            }
            else if (currentState == OperateState.SCALE || currentState == OperateState.SCALE_X || currentState == OperateState.SCALE_Y || currentState == OperateState.SCALE_Z)
            {
                Vector2 currentPosition = MousePosition;
                Vector2 v1 = MapScreenPosTo01(beginPosition);
                Vector2 v2 = MapScreenPosTo01(currentPosition);
                Vector2 direction = (v2 - v1).normalized;
                float length = (v2 - v1).magnitude;
                Vector2 leftPoint = v2 - 0.1f * length * (direction + GetVerticalVectorFrom(direction));
                Vector2 rightPoint = v2 - 0.1f * length * (direction - GetVerticalVectorFrom(direction));

                lines2D.Add(new GLLine2D() { from = v1, to = v2 });
                lines2D.Add(new GLLine2D() { from = v2, to = leftPoint });
                lines2D.Add(new GLLine2D() { from = v2, to = rightPoint });

                glDraw.DrawLine2D(lines2D, Color.white);
            }
            else if (currentState == OperateState.ROTATE || currentState == OperateState.ROTATE_X || currentState == OperateState.ROTATE_Y || currentState == OperateState.ROTATE_Z)
            {
                Vector2 currentPosition = MousePosition;
                Vector2 v1 = MapScreenPosTo01(beginPosition);
                Vector2 v2 = MapScreenPosTo01(currentPosition);
                Vector2 direction = (v2 - v1).normalized;
                float length = (v2 - v1).magnitude;
                Vector2 leftPoint = v2 + 0.1f * length * GetVerticalVectorFrom(direction);
                Vector2 rightPoint = v2 - 0.1f * length * GetVerticalVectorFrom(direction);

                lines2D.Add(new GLLine2D() { from = v1, to = v2 });
                lines2D.Add(new GLLine2D() { from = v2, to = leftPoint });
                lines2D.Add(new GLLine2D() { from = v2, to = rightPoint });

                glDraw.DrawLine2D(lines2D, Color.white);
            }

            // draw axis line
            if (isWorldSpace)
            {
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.right * 100, to = targetObjectBeginTransInfo.position + Vector3.right * 100, color = Color.red });
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.forward * 100, to = targetObjectBeginTransInfo.position + Vector3.forward * 100, color = Color.blue });
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.up * 100, to = targetObjectBeginTransInfo.position + Vector3.up * 100, color = Color.green });
            }
            else
            {
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.right * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.right * 100, color = Color.red });
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.forward * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.forward * 100, color = Color.blue });
                lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.up * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.up * 100, color = Color.green });
            }

            glDraw.DrawLine3D(lines3D, null, Color.white);
        }
    }

    private Vector2 MapScreenPosTo01(Vector3 screenPosition)
    {
        return new Vector2(screenPosition.x / (float)Screen.currentResolution.width, screenPosition.y / (float)Screen.currentResolution.height);
    }

    private float GetVectorAngleRange360(Vector3 from, Vector3 to)
    {
        // 判断叉乘的向量是否指向屏幕外判断旋转角度是否大于180
        Vector3 crossVec = Vector3.Cross(from, to);
        float dotVal = Vector3.Dot(crossVec, Vector3.back);
        float angle = Vector3.Angle(from, to);

        if (dotVal < 0)
        {
            angle = 360 - angle;
        }
        return angle;
    }

    // src在target方向上的投影
    public static Vector3 VecProject(Vector3 src, Vector3 target)
    {
        return Vector3.Dot(src, target) * target.normalized;
    }

    // 射线与平面交点
    private Vector3 GetIntersectPointWithRay(Vector3 planePoint, Vector3 n, Ray ray)
    {
        float d = Vector3.Dot(planePoint, n);
        Vector3 p0 = ray.origin;
        Vector3 d0 = ray.direction.normalized;
        float t0 = (d - Vector3.Dot(p0, n)) / Vector3.Dot(d0, n);
        return p0 + d0 * t0;
    }

    //获得二维向量的垂向量（点乘为0，有两个）
    private Vector2 GetVerticalVectorFrom(Vector2 from)
    {
        return new Vector2(-from.y, from.x).normalized;
    }
}
